using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

using MG3D.Debugger;

namespace MG3D.DX9
{
    public class Dx3dViewer : System.Windows.Forms.Control, MG3D.IViewer
    {
        #region "Public Fields"
        /// <summary>
        /// Keeps the device created for this screen
        /// </summary>
        public Device device = null;

        /// <summary>
        /// Used to initialized the device and is public to allow access to caps, presentparameter, etc...
        /// </summary>
        public DeviceSettings deviceSettings;

        /// <summary>
        /// Sets to true to render only when the Render() method is called 
        /// </summary>
        public bool RenderOnDemand = false;      

        /// <summary>
        /// Call just while the device is ready to render
        /// </summary>
        public event System.EventHandler OnRenderGeometry;	

        /// <summary>
        /// Call before the device starts to render
        /// </summary>
        public event System.EventHandler OnBeforeRender;	

        /// <summary>
        /// Call when the device needs to be reset. Usually this happens if the screen is resized or it lost the focus.
        /// </summary>
        public event System.EventHandler OnResetDevice;	
	
        /// <summary>
        /// Call when the timer ticks if it is active. (The OnBeforeRender event can raise or not it depends how fast the render goes)
        /// </summary>
        public event System.EventHandler OnFrameRateTick;	

        #endregion

        #region "Private Fields"

        private bool initFailure = false;			// Shows if it has been a problem during the initialization
        private bool deviceLost = false;
        private bool rendering = false;				// Flag to avoid call the Render method while is already rendering
        private bool frameRateTick = true;			// Flag to avoid raise the timer event before the last was processd;

        private Timer timerFrameRate = new Timer();	// Timer to adjust the framerate;
        private int frameRate = 0;					// frameRate (frames per second)

        #endregion

        #region "Constructor"
        public Dx3dViewer()
        {
            this.BackColor = Color.Black;
            this.timerFrameRate.Interval = 1000;
            this.timerFrameRate.Tick += new System.EventHandler(this.FrameRateTick);
            this.timerFrameRate.Enabled = false;
            this.FrameRate = 0;
        }

        #endregion

        #region "Properties"
        /// <summary>
        /// Returns true if the device has been iniciated correctly.
        /// </summary>
        public bool InitFailure
        {
            get { return initFailure; }
        }

        /// <summary>
        /// Adjust the frames per second
        /// </summary>
        public int FrameRate
        {
            get
            {
                return frameRate;
            }
            set
            {
                frameRate = value;
                int interval;
                if (frameRate < 1)
                {
                    this.timerFrameRate.Enabled = false;	//shuts down the timer
                }
                else
                {
                    interval = (int)(1000 / frameRate);
                    this.timerFrameRate.Interval = interval;
                    this.timerFrameRate.Enabled = true;		//Starts the timer if it wasn't already
                }
            }
        }

        /// <summary>
        /// Calculate the real framerate
        /// </summary>
        public int RealFrameRate
        {
            get
            {
                return lastFrameRate;
            }
        }

        private static int lastTick;
        private static int lastFrameRate;
        private static int actualFrameRate;

        private int CalculateFrameRate()
        {
            actualFrameRate++;

            if (System.Environment.TickCount - lastTick >= 1000)
            {
                lastTick = System.Environment.TickCount;
                lastFrameRate = actualFrameRate;
                actualFrameRate = 0;
            }

            return lastFrameRate;
        }

        #endregion

        #region "Methods"

        /// <summary>
        /// Init the device
        /// </summary>
        /// <returns>False if it has been iniciated correctly. True if it encounters any problem</returns>
        public Device InitDevice()
        {
            Trace.Report("Initializing DirectX9 Viewer Control Device.");
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

            try
            {
                deviceSettings = new DeviceSettings(this.Width, this.Height, false);
                device = new Device(0, DeviceType.Hardware, this.Handle, deviceSettings.behaviorFlags, deviceSettings.presentParams);
            }
            catch (Exception ex)
            {
                Trace.Report("Init failed! " + ex.Message);
                return null;
            }

            device.DeviceResizing += new System.ComponentModel.CancelEventHandler(OnResize);

            // avoid weird resource management issues
            Device.IsUsingEventHandlers = false;

            initFailure = false;
            Trace.Report("Init success.");

            return device;
        }

        /// <summary>
        /// Call this method when you wamt to render the scene.
        /// </summary>
        public void Render()
        {
            if (rendering || device == null || initFailure) return;
            if (deviceLost) AttemptRecovery();
            if (deviceLost) return;

            rendering = true;
            CalculateFrameRate();

            this.Text = string.Format("The framerate is {0}", lastFrameRate);

            // Raise the event
            if (OnBeforeRender != null) OnBeforeRender(this, null);

            device.BeginScene();

            //device.Clear(ClearFlags.Target | ClearFlags.ZBuffer | ClearFlags.Stencil, this.BackColor, 1.0f, 0);
            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, this.BackColor, 1.0f, 0);

            // Raise the event
            if (OnRenderGeometry != null) OnRenderGeometry(this, null);

            device.EndScene();

            try
            {
                // Copy the back buffer to the display
                device.Present();
            }
            catch (DeviceLostException)
            {
                // Indicate that the device has been lost
                deviceLost = true;
            }
            finally
            {
                rendering = false;
            }
        }


        /// <summary>
        /// Attempt to recover the device if it is lost.
        /// </summary>
        protected void AttemptRecovery()
        {
            if (device == null) return;
            try
            {
                device.TestCooperativeLevel();
            }
            catch (DeviceLostException)
            {
            }
            catch (DeviceNotResetException)
            {
                try
                {
                    device.Reset(deviceSettings.presentParams);
                    deviceLost = false;
                    if (OnResetDevice != null)
                        OnResetDevice(this, null);
                }
                catch (DeviceLostException)
                {
                    // If it's still lost or lost again, just do nothing
                    return;
                }

            }
        }

        /// <summary>
        /// If the framerate timer ticks. (Usually render de scene)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void FrameRateTick(Object sender, EventArgs e)
        {
            if (!frameRateTick) return;

            frameRateTick = false;
            if (OnFrameRateTick != null)
                OnFrameRateTick(this, null);
            frameRateTick = true;

            this.Render();
        }

        #endregion

        #region "Events"
        protected override void OnPaint(PaintEventArgs pe)
        {
            // Calling the base class OnPaint
            base.OnPaint(pe);

            if (device == null) return;
            //deltaTime = FrameworkTimer.GetElapsedTime();	// Timer que viene con el Microsoft utilities
            if (frameRate <= 0 && !RenderOnDemand)
            {
                this.Render();
                this.Invalidate();
            }
        }

        /// <summary>
        /// Cancel the default resize method
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnResize(object sender, System.ComponentModel.CancelEventArgs e)
        {
            e.Cancel = true;
        }
        #endregion

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                timerFrameRate.Dispose();
            }
            base.Dispose(disposing);
        }

    }

}
